﻿using Apollo.AST;
using Apollo.ElementUtils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Apollo.EvaluatorUtils
{
    /// <summary>
    /// 求值类
    /// </summary>
    internal class Evaluator
    {
        public bool Is_Error(Element element)
        {
            return element.Type == ElementType.ELEMENT_ERROR;
        }

        public Element New_Error(string errmsg)
        {
            return Element.New_Error(errmsg);
        }

        public Element New_Integer(int val)
        {
            return Element.New_Integer(val);
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        public Evaluator()
        {
            
        }

        /// <summary>
        /// 程序段求值
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public Element Eval(Node node)
        {
            if (node == null)
                return Element.New_Error("node is null"); 

            switch (node.NodeType)
            {
                case NodeTypeEnum.PROGRAM:
                    return Eval_Program(((AST.Program)node).m_statements);

                case NodeTypeEnum.EXPRESSION_STATEMENT:
                    return Eval(((ExpressionStatement)node).m_expression);

                case NodeTypeEnum.INTEGER:
                    return Eval_Integer((ExpressionInt)node);

                case NodeTypeEnum.INFIX:
                    Infix ptr = (Infix)node;
                    var left = Eval(ptr.m_left);
                    if (Is_Error(left))
                    {
                        return left;
                    }
                    var right = Eval(ptr.m_right);
                    if ( Is_Error(right))
                    {
                        return right;
                    }
                    return Eval_Infix(ptr.m_operator, left, right);

                default:
                    return New_Error("node type error");
            }
        }

        /// <summary>
        /// 对根节点求值
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public Element Eval_Program(List<Statement> listStatement)
        {
            Element result = null;
            foreach(var item in listStatement) 
            {
                result = Eval(item);
                if (Is_Error(result))
                {
                    break;
                }
            }
            return result;
        }

        /// <summary>
        /// 对整数求值
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        public Element Eval_Integer(ExpressionInt number)
        {
            return Element.New_Integer(number.Val);
        }

        /// <summary>
        /// 对中缀表达式求值
        /// </summary>
        /// <param name="op"></param>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        public Element Eval_Infix(string op, Element left, Element right)
        {
            switch (left.Type)
            {
                case ElementType.ELEMENT_INTEGER:
                    if (right.Type == ElementType.ELEMENT_INTEGER)
                    {
                        return Eval_Integer_Infix_Expression(op, left, right);
                    }
                    break;

                default:
                    break;
            }
            return New_Error($"Unkown operator {left.Name} {op} {right.Name}");
        }


        public Element Eval_Integer_Infix_Expression(string op, Element left, Element right)
        {
            var l = (IntegerElement)left;
            var r = (IntegerElement)right;
            switch (op)
            {
                case "+":
                    return Element.New_Integer(l.Val + r.Val);

                case "-":
                    return Element.New_Integer(l.Val - r.Val);

                case "*":
                    return Element.New_Integer(l.Val * r.Val);

                case "/":
                    return Element.New_Integer(l.Val / r.Val);

                default:
                    return Element.New_Error($"Unkonw operator {l.Val} {op} {r.Val}");
            }
        }
    }
}
